package cn.lic.tool.poi.excel;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.ServiceConfigurationError;
import java.util.Set;

public final class MappdServiceLoader<S> {
	
	private static final String PREFIX = "META-INF/services/";

	private final Class<S> service;
	private ClassLoader loader;
	private Map<String, String> names = new LinkedHashMap<>();
	private LinkedHashMap<String, S> providers = new LinkedHashMap<>();

	private MappdServiceLoader(Class<S> svc, ClassLoader cl) {
		this.service = Objects.requireNonNull(svc, "Service interface cannot be null");
		this.loader = (cl == null) ? this.getClass().getClassLoader() : cl;
		this.reload();
	}
	
	public static <S> MappdServiceLoader<S> load(Class<S> service, ClassLoader loader) {
		return new MappdServiceLoader<>(service, loader);
	}
	
	public static <S> MappdServiceLoader<S> load(Class<S> service) {
		return load(service, null);
	}
	
	private void reload() {
        synchronized(names) {
        	providers.clear();
            names.clear();
            load();
        }
	}
	
    protected void load() {
        try {
            String fullName = PREFIX + service.getName() + ".properties";
            Enumeration<URL> configs = loader.getResources(fullName);
            
            int count = 0;
            while (configs!=null && configs.hasMoreElements()) {
            	count++;
            	URL url = configs.nextElement();
            	try (InputStream stream = url.openStream()) {
            		Properties prop = new Properties();
            		prop.load(url.openStream());
            		load(prop);
            	} catch (IOException e) {
            		fail(service, "Error read config file " + url.toString(), e);
            	}
            }
            POILoggerUtil.info("加载SPI文件" + fullName + ", 数量 " + count );
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    
    public void load(Properties prop) {
    	POILoggerUtil.info("加载服务配置列表 properties:", prop);
    	prop.entrySet().forEach(entry -> {
    		POILoggerUtil.info("加载服务配置:", entry);
			String cname = names.putIfAbsent(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
			if (cname != null) {
				fail(service, "Duplicate alias: " + entry.getKey());
			}
		});
    }
    public S getService(String alias) {
    	S s = providers.get(alias);
    	POILoggerUtil.info("getService " + alias + " = " + s);
    	POILoggerUtil.info("getService names =" + names);

    	if (s == null && names.containsKey(alias)) {
    		synchronized(providers) {
    			if (!providers.containsKey(alias)) {
    				s = createService(alias);
    				providers.put(alias, s);
    			}
    		}
    	}
    	return s;
    }
    
    public Set<String> getAlias() {
    	return names.keySet();
    }
    
    public boolean containsService(String alias) {
    	return names.containsKey(alias);
    }
    
    protected S createService(String alias) {
    	POILoggerUtil.info("createService alias =" + alias);

    	if (!names.containsKey(alias)) {
    		fail(service, "service alias " + alias + " not found");
    	}
    	
    	String name = names.get(alias);
    	Class<?> clazz = null;
    	 try {
    		 clazz = Class.forName(name, false, loader);
         } catch (ClassNotFoundException x) {
             fail(service, "Provider class " + name + " not found");
         }
    	 
         if (!service.isAssignableFrom(clazz)) {
             fail(service, "Provider class " + name  + " not a subtype");
         }
         
         try {
             return service.cast(clazz.newInstance());
         } catch (Throwable x) {
             fail(service, "Provider class " + name + " could not be instantiated", x);
         }
         
         return null;
    }
    
    private static void fail(Class<?> service, String msg)
            throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }
    private static void fail(Class<?> service, String msg, Throwable cause)
            throws ServiceConfigurationError {
            throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                                cause);
    }
}
